@echo off
md tmp 2>nul
more +15 %0 > tmp\%~n0.cpp
set cxxoptimize=-Wno-invalid-offsetof -msse -msse2 -O3 -ftree-vectorize -funsafe-math-optimizations -ffast-math -fomit-frame-pointer -funsafe-loop-optimizations
gcc -c -o tmp\bitop.o bitop.c
g++ -c -o tmp\strop.o strop.cpp %cxxoptimize%
g++ -c -o tmp\common-func.o common\func.cpp %cxxoptimize%
g++ -c -o tmp\%~n0.o tmp\%~n0.cpp %cxxoptimize%
g++ tmp\*.o -o %~n0.exe -Wl,-Bstatic -static-libgcc  -lluajit -Wno-unused-result
:: rd /s /q tmp
strip %~n0.exe
%~n0.exe
pause > nul
goto :eof

#include <windows.h>
#include <iostream>
#include <string>
#include <cstdlib>
#include <stack>
#include "../console-common.h"

int luaopen_bit(lua_State *L);
int luacon_extend_str_api(lua_State *L);

#define SETFIELD_INT(L, NAME)\
	lua_pushinteger(L, NAME);\
	lua_setfield(L, -2, #NAME)
	
#define SETFIELD_INTX(L, NAME, VALUE)\
	lua_pushinteger(L, VALUE);\
	lua_setfield(L, -2, #NAME)
	
#define SET_ALIAS(L, NAME, ALIAS)\
	lua_getglobal(L, NAME);\
	lua_setglobal(L, ALIAS)

#define SET_CFUNCTION(L, NAME, func)\
	(lua_pushcfunction(L, func),\
	lua_setfield(L, -2, NAME))

HANDLE hOut;
std::stack <int> attr_stack;

const char* lua_tostring_ext (lua_State * L, int n)
{
	switch (lua_type(L, n))
	{
		case LUA_TNUMBER:
		case LUA_TSTRING:
			return lua_tostring(L, n);
		case LUA_TBOOLEAN:
			return (lua_toboolean(L, n) ? "true" : "false");
			break;
		case LUA_TNIL:
			return "nil";
		default:
			lua_pushfstring(L, "%s: %p", luaL_typename(L, n), lua_topointer(L, n));
			const char* s = lua_tostring(L, -1);
			lua_pop(L, 1);
			return s;
	}
}

int luacon_output (lua_State * l)
{
	int args = lua_gettop(l);
	
	if (args >= 1)
	{
		CONSOLE_SCREEN_BUFFER_INFO buf_info;
		if (args >= 2)
		{
			GetConsoleScreenBufferInfo( hOut, &buf_info );
			SetConsoleTextAttribute( hOut, lua_tonumber(l, 2) );
		}
		std::cout << lua_tostring_ext(l, 1) << std::endl;
		if (args >= 2)
		{
			SetConsoleTextAttribute( hOut, buf_info.wAttributes );
		}
	}
	return 0;
}

int luacon_wait (lua_State * l)
{
	Sleep(luaL_checkint(l, 1));
	return 0;
}

#define luaL_identity lua_gettop

int luacon_pushattr (lua_State * l)
{
	int args = lua_gettop(l);
	CONSOLE_SCREEN_BUFFER_INFO buf_info;
	GetConsoleScreenBufferInfo( hOut, &buf_info );
	if (args >= 1)
		SetConsoleTextAttribute( hOut, luaL_checkint(l, 1) );
	attr_stack.push( buf_info.wAttributes );
	return 0;
}

int luacon_setattr (lua_State * l)
{
	int args = lua_gettop(l);
	CONSOLE_SCREEN_BUFFER_INFO buf_info;
	GetConsoleScreenBufferInfo( hOut, &buf_info );
	if (args >= 1)
	{
		SetConsoleTextAttribute( hOut, luaL_checkint(l, 1) );
		return 0;
	}
	lua_pushinteger(l, buf_info.wAttributes);
	return 1;
}

bool luacon_resetattr_check (lua_State * l)
{
	int attr; bool retv = true;
	if (attr_stack.empty()) attr = 7, retv = false;
	else attr = attr_stack.top();
	SetConsoleTextAttribute( hOut, attr );
	return retv;
}

int luacon_resetattr (lua_State * l)
{
	luacon_resetattr_check (l);
	return 0;
}

int luacon_popattr (lua_State * l)
{
	if (luacon_resetattr_check (l)) attr_stack.pop();
	return 0;
}

int luacon_system (lua_State * l)
{
	int args = lua_gettop(l);
	if (args < 1)
		return luaL_error(l, "Must be least one argument");
	const char* cmdline = luaL_checkstring(l, 1);
	system(cmdline);
	return 0;
}

int luacon_drawchr (lua_State * l)
{
	int args = lua_gettop(l);
	int arg1, // character
		arg2, // position x
		arg3, // position y
		arg4, // number of times to write
		endx, i;

	CONSOLE_SCREEN_BUFFER_INFO csbi;
	GetConsoleScreenBufferInfo( hOut, &csbi );

	int XCHRS = csbi.dwSize.X;
	int YCHRS = csbi.dwSize.Y;

	if (args >= 3)
	{
		arg2 = lua_tointeger(l, 2);
		arg3 = lua_tointeger(l, 3);
	}
	else
		return luaL_error(l, "Must be least 3 arguments");
	arg4 = luaL_optint(l, 4, 1);
	switch (lua_type(l, 1))
	{
	case LUA_TNUMBER:
		arg1 = lua_tointeger(l, 1);
		break;
	case LUA_TSTRING:
		arg1 = lua_tostring(l, 1)[0];
		break;
	default:
		return luaL_error(l, "argument #1 must be number or string");
		break;
	}
	if (arg4 < 0) arg4 = 0;
	if (arg3 >= 0 && arg3 < YCHRS)
	{
		endx = arg2 + arg4;
		if (arg2 < 0) arg2 = 0;
		if (endx > XCHRS) endx = XCHRS;
		COORD coordScreen = { arg2, arg3 };
		FillConsoleOutputAttribute( hOut, csbi.wAttributes, endx - arg2, coordScreen, NULL );
		FillConsoleOutputCharacter( hOut, (TCHAR) arg1, endx - arg2, coordScreen, NULL );
	}
	return 0;
}

#define FLAG_READ_ATTR	0x1

int luacon_readchr (lua_State * l)
{
	int args = lua_gettop(l);
	int x, y, f, p, ra, beg;

	if (args >= 2)
		x = lua_tointeger(l, 1),
		y = lua_tointeger(l, 2);
	else
		return luaL_error(l, "Must be least 2 arguments");

	CONSOLE_SCREEN_BUFFER_INFO csbi;
	GetConsoleScreenBufferInfo( hOut, &csbi );

	f = luaL_optint(l, 3, 0);
	ra = (f & FLAG_READ_ATTR) ? 2 : 1;

	if (x >= 0 && y >= 0 && x < csbi.dwSize.X && y < csbi.dwSize.Y)
	{
		COORD coordScreen = {x, y};
		TCHAR ochar[5] = {0};
		WORD oattr[5] = {0};
		DWORD reserved;
		ReadConsoleOutputCharacter( hOut, ochar, 1, coordScreen, &reserved );
		lua_pushinteger(l, ochar[0]);
		if (f & FLAG_READ_ATTR)
		{
			ReadConsoleOutputAttribute( hOut, oattr, 1, coordScreen, &reserved );
			lua_pushinteger(l, oattr[0]);
		}
	}
	else
	{
		lua_pushnil(l);
		if (f & FLAG_READ_ATTR) lua_pushnil(l);
	}
	return ra;
}

#define CHECKSTRARG(L, x, y, s, v) \
	if (args >= 3) { size_t _sz; x = lua_tointeger(L, 2); y = lua_tointeger(L, 3);\
	int _align = luaL_optinteger(L, 4, -1); s = luaL_checklstring(L, 1, &_sz);\
	len = _sz; (_align > 0) ? (v -= len) : (_align == 0) && (v += -len>>1);\
	} else return luaL_error(L, "Must be least 3 arguments");

int luacon_drawstr (lua_State * l)
{
	int args = lua_gettop(l);
	const char* arg1;	// string
	int arg2, arg3,		// position
		endx, i, len, j = 0;
	size_t sz;

	CONSOLE_SCREEN_BUFFER_INFO csbi;
	GetConsoleScreenBufferInfo( hOut, &csbi );

	int XCHRS = csbi.dwSize.X;
	int YCHRS = csbi.dwSize.Y;

	CHECKSTRARG(l, arg2, arg3, arg1, arg2)
	if (arg3 >= 0 && arg3 < YCHRS)
	{
		endx = arg2 + len;
		if (arg2 < 0) j = -arg2, arg2 = 0;
		if (endx > XCHRS) endx = XCHRS;
		COORD coordScreen = { arg2, arg3 };
		FillConsoleOutputAttribute( hOut, csbi.wAttributes, endx - arg2, coordScreen, NULL );
		WriteConsoleOutputCharacter( hOut, (LPCTSTR) (arg1 + j), endx - arg2, coordScreen, NULL );
	}
	return 0;
}

int luacon_drawstrv (lua_State * l)
{
	int args = lua_gettop(l);
	const char* arg1;	// string
	int arg2, arg3,		// position
		endx, i, len, j = 0;
	size_t sz;

	CONSOLE_SCREEN_BUFFER_INFO csbi;
	GetConsoleScreenBufferInfo( hOut, &csbi );

	int XCHRS = csbi.dwSize.X;
	int YCHRS = csbi.dwSize.Y;
	int ATTRS = csbi.wAttributes;

	CHECKSTRARG(l, arg2, arg3, arg1, arg3)
	if (arg2 >= 0 && arg2 < XCHRS)
	{
		endx = arg3 + len;
		if (arg3 < 0) j = -arg3, arg3 = 0;
		if (endx > YCHRS) endx = YCHRS;
		COORD coordScreen = { arg2, arg3 };
		while (coordScreen.Y < endx)
		{
			FillConsoleOutputAttribute( hOut, ATTRS, 1, coordScreen, NULL );
			FillConsoleOutputCharacter( hOut, arg1[j], 1, coordScreen, NULL );
			coordScreen.Y ++; j ++;
		}
	}
	return 0;
}

int luacon_cls (lua_State * l)
{
	HANDLE hConsole = hOut;
	COORD coordScreen = { 0, 0 }; /* here's where we'll home the cursor */
	BOOL bSuccess;
	DWORD cCharsWritten;
	CONSOLE_SCREEN_BUFFER_INFO csbi; /* to get buffer info */
	DWORD dwConSize; /* number of character cells in the current buffer */

	/* get the number of character cells in the current buffer */
	GetConsoleScreenBufferInfo( hConsole, &csbi );

	dwConSize = csbi.dwSize.X * csbi.dwSize.Y;

	/* fill the entire screen with blanks */
	FillConsoleOutputCharacter( hConsole, (TCHAR) ' ', dwConSize, coordScreen, &cCharsWritten );

	/* get the current text attribute */
	GetConsoleScreenBufferInfo( hConsole, &csbi );

	/* now set the buffer's attributes accordingly */
	FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten );

	/* put the cursor at (0, 0) */
	SetConsoleCursorPosition( hConsole, coordScreen );
	return 0;
}

int main (int argc, char** argv)
{
	char* buffer = (char*)malloc(50000+1);
	hOut = GetStdHandle(STD_OUTPUT_HANDLE);
	if (argc >= 2 && !strncmp(argv[1], "--print,", 8))
	{
		int c, ch;
		int k = sscanf(argv[1]+8, "%d", &c);
		if (k)
		{
			CONSOLE_SCREEN_BUFFER_INFO info;
			GetConsoleScreenBufferInfo( hOut, &info );
			int oc = info.wAttributes;
			SetConsoleTextAttribute( hOut, c );
			std::cin.getline(buffer, 50000);
			std::cout << buffer << std::endl;
			SetConsoleTextAttribute( hOut, oc );
		}
		return 0;
	}
	
	SetConsoleTitle("Lua console");

	lua_State *L = luaL_newstate();

	luaL_openlibs(L);
	
	lua_pushcfunction(L, luacon_output);
	lua_setglobal(L, "print");

	const static struct luaL_Reg console_lua_api [] = {
		{"system", luacon_system},

		{"draw_char", luacon_drawchr},
		{"read_char", luacon_readchr},
		{"draw_string", luacon_drawstr},
		{"draw_str", luacon_drawstr},
		{"draw_str_v", luacon_drawstrv},
		{"clear", luacon_cls},

		{"delay", luacon_wait},
		{"cmd2rgb", luaL_identity},
		{"ident", luaL_identity},
		{NULL, NULL}
	};

	luaL_register(L, "console", console_lua_api);
	SET_ALIAS(L, "console", "con");
	// SETFIELD_INT(L, FLAG_RESET_STACK);
	SETFIELD_INT(L, FLAG_READ_ATTR);

	SETFIELD_INT(L, FOREGROUND_BLUE);
	SETFIELD_INT(L, FOREGROUND_GREEN);
	SETFIELD_INT(L, FOREGROUND_RED);
	SETFIELD_INT(L, FOREGROUND_INTENSITY);

	SETFIELD_INT(L, BACKGROUND_BLUE);
	SETFIELD_INT(L, BACKGROUND_GREEN);
	SETFIELD_INT(L, BACKGROUND_RED);
	SETFIELD_INT(L, BACKGROUND_INTENSITY);
	
	const static struct luaL_Reg console_additional_api [] = {
		{"push", luacon_pushattr},
		{"pop", luacon_popattr},
		{"set", luacon_setattr},
		{"reset", luacon_resetattr},
		{"attribute", NULL},
		{NULL, NULL}
	};

	luaL_register_ext(L, console_additional_api);
	lua_getfield(L, -1, "attribute");
	lua_setfield(L, -2, "attr");

	luaopen_bit(L);
	luacon_extend_str_api(L);
	
	lua_settop(L, 0);

	if (argc > 1)
	{
		lua_pop(L, luaL_dofile(L, argv[1]));
	}
	else
	{
		std::string lastCode = "";
	continue_lua:
		if (lastCode.length())
			lastCode += "\n";
		std::cin.getline(buffer, 50000);
		lastCode += buffer;
		std::string tmp = "return " + lastCode;
		luaL_loadbuffer(L, tmp.c_str(), tmp.length(), "@console");
		if (lua_type(L, -1) != LUA_TFUNCTION)
		{
			lua_pop(L, 1);
			luaL_loadbuffer(L, lastCode.c_str(), lastCode.length(), "@console");
		}
		if (lua_type(L, -1) != LUA_TFUNCTION)
		{
			std::string err = lua_tostring_ext(L, -1);
			if (err.find("near '<eof>'") != err.npos) //the idea stolen from lua-5.1.5/lua.c
				std::cout << "..." << std::endl;
			else
			{
				std::cerr << err << std::endl;
				lastCode = "";
			}
		}
		else
		{
			lastCode = "";
			int ret = lua_pcall(L, 0, LUA_MULTRET, 0), level, top;
			if (ret)
			{
				std::cerr << lua_tostring_ext(L, -1) << std::endl;
			}
			else
			{
				top = lua_gettop(L);
				for (level = 1; level <= top; level++)
					std::cout << lua_tostring_ext(L, level) << std::endl;
			}
		}
		lua_settop(L, 0);
		goto continue_lua;
	}
	
	lua_close(L);
	free(buffer);
}
